VPN 与 LocalDevVPN
VPN 是注入主 App 活跃脚本的全局对象,无需导入。VPN.getState() 查询设备的 Scripting VPN;VPN.start()、VPN.stop() 启动或停止它,需要独立的 vpn 权限。查询不申请控制权限。
下面的示例会启动 Scripting 自身 VPN。已有外部 VPN 提供反射时,请跳过这段,直接使用下方 RSD 隧道 API。
start() 在系统报告已连接后完成;connecting、reasserting、disconnecting 过渡态拒绝重复启动。stop() 在停止偏好处理完成且系统断开后完成,并关闭绑定 Scripting 反射的 RSD 会话。脚本退出不会停止已接受的设备级控制操作。等待超时可能在操作仍继续时拒绝,应先查询状态再决定是否重试。已连接不代表流量成功。
VPN.localDevVPN.getState() 查询保存意图和实际回环反射状态。setEnabled(enabled, {expectedState?}) 只修改设备意图,保留地址;断开时修改不会启动 VPN,配置切换和脚本退出也不会清除意图。控制本地设备意图及 RSD 使用 localDevVPN 权限,与 vpn 分开。权限错误保留适用的 code、reason、permission、permissionScope 字段。
过期 token 拒绝且不覆盖意图。省略 token 时以原子写入前的状态为基准。采样期间发生并发变化也会拒绝。保存成功后若查询失败,Promise 仍可能拒绝;应重新查询确认,而非认为没有保存。
RSD 隧道
本机反射与普通联网
VPN.localDevVPN.setEnabled(true) 只启用本机反射意图;VPN.start() 启动的是当前选中的完整 Scripting 代理配置,包含其路由规则与 DNS 策略。启用反射不会自动把该配置变成仅反射隧道,也不会自动允许其他流量直连。请确保所选配置允许普通联网;拒绝其他目的地址的隔离测试配置不适合地理编码和网页请求。
外部 App 提供反射时,保持其 VPN 连接,直接调用 openRSDTunnel。普通联网由外部 App 的规则与 DNS 决定,Scripting 不修改这些设置。此时 Scripting 的 effectiveStatus: "inactive" 属于预期状态。
Location.reverseGeocode 使用系统地理编码服务。kCLErrorDomain 错误 2 可能表示网络问题,也可能是 App 级限频。应缓存结果、合并进行中查询,避免地图相机每次更新都查询,也不要快速连续重试。错误 8 表示没有匹配结果。排查时,以低频率对同一公共坐标分别测试无 VPN、允许普通流量 DIRECT 的配置以及外部 VPN。网页请求成功不能单独证明地理编码服务的后端路径正常,还要记录反查本身的结果。
打开与收发
VPN.localDevVPN.openRSDTunnel({host, port, preSharedKey, identity?, mtu?}) 需要当前路由可达的数字 IPv4 反射地址、localDevVPN 权限和配对流程已获取的 32 字节 PSK(Uint8Array 或 ArrayBuffer)。identity 可选,为不超过 128 字节的 UTF-8 字符串。MTU 默认 16000,范围 1280–16384。它提供 CDTunnel 传输及有界的私有 IPv6 TCP 连接,不负责 Apple 设备发现、配对或 RSD 服务协议。
最多一个隧道、四个 TCP 连接。每次读写最多 16384 字节,每个连接最多一个待完成读取和写入。read 的 maximum 为 1–16384,空闲读取 120 秒超时。所有操作仅允许原始活跃主 App 脚本。脚本退出会关闭隧道;自身 VPN 停止及相关意图/配置变化只关闭绑定 Scripting 反射的会话。close 等待本会话清理,旧句柄不能关闭替代会话。传输失败时已验证的缓存字节仍可先读出,再报告错误。getState 提供协商地址、MTU、RSD 端口、状态和有界缓存数量。请保留句柄并在结束时关闭。
迁移
独立全局 LocalDevVPN 已移除。将 LocalDevVPN.getState()、LocalDevVPN.setEnabled(...) 改为 VPN.localDevVPN.getState()、VPN.localDevVPN.setEnabled(...);移除从 scripting 导入这些 API 的代码,参考最新文档与 SDK 的全局声明。设备意图/RSD 权限仍为 localDevVPN,VPN 启停权限为 vpn。
VPN 的状态查询、启动和停止分别最多允许一个待完成请求;启动期间仍可查询状态或停止。隧道最多允许四个待完成的连接请求;隧道的状态查询与关闭、连接的 end 与 close 分别最多允许一个同类待完成请求。再次发起同类请求前请等待完成。
外部 VPN 提供本机反射
可以先连接其他代理 App 或 wloc 的本机隧道,再直接调用全局 VPN.localDevVPN.openRSDTunnel,无需 import,也无需调用 VPN.start() 或要求 effectiveStatus === "active"。getState() 仍只描述 Scripting 自身,不读取或控制其他 App 的 VPN。实际 PSK 握手和服务回包用于证明可达性;不会提前探测一次性 listener。host 必须是有效的数值 IPv4,特殊、环回、组播及 FakeIP 地址会拒绝。配对流程需先取得本次 PSK 和新的 listener 端口。
脚本退出和显式 close 会释放所有路径的会话。只有绑定已验证 Scripting 反射的会话会随自身 VPN 停止或相关配置变更关闭;外部路由会话不受这些开关影响,真实传输失败仍会报告。VPN.stop() 只停止 Scripting 自身。
RSD 生命周期诊断
await tunnel.getState() 通过 closedReason 表示显式生命周期清理,通过 terminalError 保留首次底层终止故障,没有故障时为 null。后续显式清理不会抹掉此前故障。诊断包含 code、stage、nativeDomain、nativeCode,可选 networkErrorCategory 与数字 networkErrorCode。RSD Promise 的错误也附带相同诊断字段,已关闭会话的 reject 还提供 closedReason;权限错误保留原有权限字段。
常见错误为 RSD_SESSION_CLOSED、RSD_STREAM_CLOSED、RSD_TRANSPORT_EOF、RSD_TRANSPORT_TIMEOUT、RSD_TCP_TIMEOUT、RSD_PEER_RESET、RSD_NETWORK_FAILURE、RSD_PROTOCOL_ERROR、RSD_CANCELLED。不能仅凭 NSError 数字(包括 0)判断 iOS 已终止或挂起 App。单条 TCP reset 不一定关闭整个隧道,应同时查看操作错误和隧道状态;保活状态不能代替该检查。
处理访问错误
网络操作会在开始前检查访问权限,可能抛出或拒绝并返回 error.code === "ERR_PRO_REQUIRED"。连接或监听前应安装 socket/server 错误回调,并捕获 Bonjour、VPN API 的 Promise 拒绝。请将清理放在 finally 中:访问权限到期后仍可关闭 socket 和 RSD 会话、停止 Bonjour 资源、调用 VPN.stop() 或关闭 VPN.localDevVPN。
打开期限与取消
openRSDTunnel({...options, timeout:5000}) 可设置毫秒期限(100...120000 的整数),从授权完成开始,覆盖旧连接清理等待、路由采样、TLS 和会话发布,权限弹窗耗时不计入。省略时保留原有策略,包括 transport 的 10 秒启动计时。到期拒绝,错误为 RSD_OPEN_TIMEOUT / TimeoutError。
await VPN.localDevVPN.cancelRSDTunnelOpening() 只取消当前 context 的待打开请求并等待原生清理;已建立会话仍需用 tunnel.close()。打开请求以 RSD_CANCELLED / AbortError 拒绝。若脚本权限弹框在等待用户,取消会阻止网络准入,完成仍需等弹框结束。访问失效后仍可清理;重试前必须重新创建配对 listener。
期限到达会触发取消;拒绝需等待必要原生清理完成,因此实际返回可能稍晚。
